package phases

import (
	"runtime/debug"

	"github.com/pkg/errors"
	confv1beta1 "gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	kerrors "k8s.io/apimachinery/pkg/util/errors"
	"sigs.k8s.io/cluster-api/util"
	ctrl "sigs.k8s.io/controller-runtime"

	bkev1beta1 "gopkg.openfuyao.cn/cluster-api-provider-bke/api/v1beta1"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/mergecluster"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/pkg/phaseframe/phaseutil"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/annotation"
	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils/constant"
)

type PhaseFlow struct {
	BKEPhases []phaseframe.Phase
	ctx       *phaseframe.PhaseContext
	// oldBKECluster record the old BKECluster
	oldBKECluster *bkev1beta1.BKECluster
	// newBKECluster record the current BKECluster
	newBKECluster *bkev1beta1.BKECluster
}

func init() {
	FullPhasesRegisFunc = append(FullPhasesRegisFunc, CommonPhases...)
	FullPhasesRegisFunc = append(FullPhasesRegisFunc, DeployPhases...)
	FullPhasesRegisFunc = append(FullPhasesRegisFunc, PostDeployPhases...)
}

var FullPhasesRegisFunc []func(ctx *phaseframe.PhaseContext) phaseframe.Phase

func NewPhaseFlow(ctx *phaseframe.PhaseContext) *PhaseFlow {
	return &PhaseFlow{
		ctx: ctx,
	}
}

// CalculatePhase is used to calculate the phase which need to be executed
func (p *PhaseFlow) CalculatePhase(old *bkev1beta1.BKECluster, new *bkev1beta1.BKECluster) error {
	var phasesFuncs []func(ctx *phaseframe.PhaseContext) phaseframe.Phase
	if phaseutil.IsDeleteOrReset(p.ctx.BKECluster) {
		phasesFuncs = DeletePhases
	} else {
		phasesFuncs = FullPhasesRegisFunc
	}

	// 计算需要执行的阶段，并添加到status中
	for _, f := range phasesFuncs {
		phase := f(p.ctx)
		if phase.NeedExecute(old, new) {
			p.BKEPhases = append(p.BKEPhases, phase)
		}
	}

	return p.ReportPhaseStatus()
}

// ReportPhaseStatus is used to report the phase status
func (p *PhaseFlow) ReportPhaseStatus() error {
	if p.BKEPhases == nil || len(p.BKEPhases) == 0 {
		return nil
	}

	if p.ctx.BKECluster.Status.PhaseStatus != nil {
		// 上报前先找到最后一个成功的
		var lastSuccessPhaseIndex int

		for i, phaseStatus := range p.ctx.BKECluster.Status.PhaseStatus {
			if phaseStatus.Status == bkev1beta1.PhaseSucceeded {
				lastSuccessPhaseIndex = i
			}
		}

		// 移除成功后除了失败的phase
		last := lastSuccessPhaseIndex + 1
		for last < len(p.ctx.BKECluster.Status.PhaseStatus) && p.ctx.BKECluster.Status.PhaseStatus[last].Status != bkev1beta1.PhaseFailed {
			last++
		}

		p.ctx.BKECluster.Status.PhaseStatus = p.ctx.BKECluster.Status.PhaseStatus[:last]

		// 最多保留20个phase，避免重复执行时，phaseStatus过多
		if len(p.ctx.BKECluster.Status.PhaseStatus) > 20 {
			p.ctx.BKECluster.Status.PhaseStatus = p.ctx.BKECluster.Status.PhaseStatus[len(p.ctx.BKECluster.Status.PhaseStatus)-20:]
		}
	}

	// 上报
	waitPhaseCount := 0
	p.ctx.Log.Debug("*****Finish calculate phases****")
	for _, phase := range p.BKEPhases {
		if err := phase.Report("", true); err != nil {
			return err
		}
		if phase.GetStatus() == bkev1beta1.PhaseWaiting {
			p.ctx.Log.Debug("phase %s    ->     %s", phase.Name(), bkev1beta1.PhaseWaiting)
			waitPhaseCount++
		}
	}
	p.ctx.Log.Debug("*****All of %d phases wait******", waitPhaseCount)

	//更新后记录oldBKECluster, newBKECluster
	return p.refreshOldAndNewBKECluster()
}

func (p *PhaseFlow) Execute() (_ ctrl.Result, err error) {
	defer func() {
		if e := recover(); e != nil {
			debug.PrintStack()
			if recoverErr, ok := e.(error); ok {
				err = errors.Errorf("panic: %v", recoverErr)
			}
		}
	}()

	var phases confv1beta1.BKEClusterPhases

	// 从bkeCluster中获取需要执行的phase
	if phaseutil.IsDeleteOrReset(p.ctx.BKECluster) {
		phases = ClusterDeleteResetPhaseNames
	} else {
		phases = p.getWaitingPhases()
	}

	go p.ctx.WatchBKEClusterStatus()

	// phases 是从状态中读到的等待的phase名字列表
	// p.BKEPhases 是本轮通过NeedExecute判断要执行的phase
	// 有时由于某一phase返回错误，会导致部分未执行的phase为waiting状态
	// 并且在下一轮的计算中，这些waiting的phase可能不需要执行了，需要给他的状态更为unknown
	defer func() {

		if len(phases) > 0 {
			for i, phase := range p.ctx.BKECluster.Status.PhaseStatus {
				if phase.Name.In(phases) {
					p.ctx.BKECluster.Status.PhaseStatus[i].Status = bkev1beta1.PhaseUnknown
				}
			}
			if err := mergecluster.SyncStatusUntilComplete(p.ctx.Client, p.ctx.BKECluster); err != nil {
				return
			}
		}
	}()

	errs := []error{}
	res := ctrl.Result{}

	for _, phase := range p.BKEPhases {
		p.ctx.Log.NormalLogger.Debugf("waiting phases num: %d", len(phases))
		p.ctx.Log.NormalLogger.Infof("current phase name: %s", phase.Name())

		if phase.Name().In(phases) {
			// 移除该phasename从phases中
			phases.Remove(phase.Name())

			phase.RegisterPreHooks(
				calculatingClusterPreStatusByPhase,
				registerPhaseCName,
			)
			phase.RegisterPostHooks(calculatingClusterPostStatusByPhase)

			// 实时计算是否需要执行，避免前置的phase对BKECluster的状态修改导致后续phase不执行
			if phase.NeedExecute(p.oldBKECluster, p.newBKECluster) {

				// 执行前置hook，设置phase状态为running，设置开始时间,上报
				if err = phase.ExecutePreHook(); err != nil {
					return res, err
				}

				// 执行
				phaseResult, phaseErr := phase.Execute()
				if phaseErr != nil {
					err = phaseErr
					errs = append(errs, phaseErr)
				}
				res = util.LowestNonZeroResult(res, phaseResult)
			} else {
				phase.SetStatus(bkev1beta1.PhaseSkipped)
			}

		} else {
			phase.SetStatus(bkev1beta1.PhaseSkipped)
		}

		if phase.GetStatus() == bkev1beta1.PhaseSkipped {
			p.ctx.Log.Debug("********************************")
			p.ctx.Log.Debug("phase %s    ->     %s", phase.Name(), bkev1beta1.PhaseSkipped)
			p.ctx.Log.Debug("********************************")
			if err := phase.Report("", false); err != nil {
				return ctrl.Result{}, err
			}
			continue
		}

		// 执行后置hook，设置phase状态为success或者failed，设置结束时间,上报
		err = phase.ExecutePostHook(err)
		if err != nil {
			errs = append(errs, err)
		}

		logFinishWhenDeployFailed(p.ctx)

		if err = p.refreshOldAndNewBKECluster(); err != nil {
			errs = append(errs, err)
		}
		if len(errs) > 0 {
			err = kerrors.NewAggregate(errs)
			return res, err
		}
	}

	return res, nil
}

func (p *PhaseFlow) refreshOldAndNewBKECluster() error {
	oldBkeCluster, err := mergecluster.GetLastUpdatedBKECluster(p.ctx.BKECluster)
	if err != nil {
		return err
	}
	p.oldBKECluster = oldBkeCluster
	p.newBKECluster = p.ctx.BKECluster.DeepCopy()
	return nil
}

func (p *PhaseFlow) getWaitingPhases() confv1beta1.BKEClusterPhases {
	phases := confv1beta1.BKEClusterPhases{}
	for _, phase := range p.ctx.BKECluster.Status.PhaseStatus {
		if phase.Status == bkev1beta1.PhaseWaiting {
			phases.Add(phase.Name)
		}
	}
	return phases
}

func calculatingClusterPreStatusByPhase(phase phaseframe.Phase) error {
	ctx := phase.GetPhaseContext()
	//defer func() {
	//	// 设置一个标识，配合状态记录器，从而忽略phase运行中的状态被重复记录
	//	if ctx.BKECluster.Status.ClusterStatus != bkev1beta1.ClusterUnknown {
	//		annotation.SetAnnotation(ctx.BKECluster, annotation.StatusRecordAnnotationKey, "")
	//	}
	//}()
	if phase.Name() == EnsureClusterName {
		ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterChecking
		return nil
	}
	return calculateClusterStatusByPhase(phase, nil)
}

func calculatingClusterPostStatusByPhase(phase phaseframe.Phase, err error) error {
	defer func() {
		ctx := phase.GetPhaseContext()
		// 设置一个标识，配合状态记录器，从而忽略phase运行中的状态被重复记录
		if ctx.BKECluster.Status.ClusterStatus != bkev1beta1.ClusterUnknown {
			annotation.SetAnnotation(ctx.BKECluster, annotation.StatusRecordAnnotationKey, "")
		}
	}()
	return calculateClusterStatusByPhase(phase, err)
}

func calculateClusterStatusByPhase(phase phaseframe.Phase, err error) error {
	phaseName := phase.Name()
	ctx := phase.GetPhaseContext()

	switch {
	case phaseName.In(CustomSetStatusPhaseNames):
		return nil
	case phaseName.In(ClusterInitPhaseNames):
		if err != nil {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterInitializationFailed
		} else {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterInitializing
		}
	case phaseName.In(ClusterScaleMasterUpPhaseNames):
		if err != nil {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterScaleFailed
		} else {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterMasterScalingUp
		}
	case phaseName.In(ClusterScaleWorkerUpPhaseNames):
		if err != nil {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterScaleFailed
		} else {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterWorkerScalingUp
		}
	case phaseName.In(ClusterDeletePhaseNames):
		if err != nil {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterDeleteFailed
		} else {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterDeleting
		}
	case phaseName.In(ClusterPausedPhaseNames):
		if err != nil {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterPauseFailed
		} else {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterPaused
		}
	case phaseName.In(ClusterDryRunPhaseNames):
		if err != nil {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterDryRunFailed
		} else {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterDryRun
		}
	case phaseName.In(ClusterAddonsPhaseNames):
		if err != nil {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterDeployAddonFailed
		} else {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterDeployingAddon
		}
	case phaseName.In(ClusterUpgradePhaseNames):
		if err != nil {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterUpgradeFailed
		} else {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterUpgrading
		}
	case phaseName.In(ClusterScaleMasterDownPhaseNames):
		if err != nil {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterScaleFailed
		} else {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterMasterScalingDown
		}
	case phaseName.In(ClusterScaleWorkerDownPhaseNames):
		if err != nil {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterScaleFailed
		} else {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterWorkerScalingDown
		}
	case phaseName.In(ClusterManagePhaseNames):
		if err != nil {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterManageFailed
		} else {
			ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterManaging
		}
	default:
		ctx.Log.Debug("unknown phase %s", phaseName.String())
		ctx.BKECluster.Status.ClusterStatus = bkev1beta1.ClusterUnknown
	}
	return nil
}

func logFinishWhenDeployFailed(phaseContext *phaseframe.PhaseContext) {
	_, _, bkeCluster, _, log := phaseContext.Untie()
	if bkeCluster.Status.ClusterHealthState == bkev1beta1.DeployFailed {
		log.Finish(constant.ClusterDeployFailedReason, "Cluster deploy failed, process exit")
	}
}

func registerPhaseCName(phase phaseframe.Phase) error {
	cName := ConvertPhaseNameToCN(phase.Name().String())
	phase.SetCName(cName)
	return nil
}
